﻿using DBUtil.Net.Attributes;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;

namespace DBUtil.Net
{
    /// <summary>
    /// 从 Class 实体定义中分析到的表信息
    /// </summary>
    public class EntityInfo
    {
        #region TableName & SchemaName & FullName
        /// <summary>
        /// [Table("t_test")] 中配置的名字 或 类名
        /// </summary>
        /// <remarks>
        /// 注意: 这个 <c>TableNamePure</c> 不能直接用在sql拼接中. sql拼接时请使用 <seealso cref="TableNameSeg"/><br/>
        /// 另外, [Table("t_test")] 中的 t_test 应该是 db 里的纯净表名, 不要使用 "", [], `` 等包裹
        /// </remarks>
        public string TableNamePure { get; set; }
        /// <summary>
        /// [Table(Schema = "dbo")] 中配置的名字, 没有配置则为null
        /// </summary>
        /// <remarks>
        /// 注意: [Table(Schema = "dbo")] 中的 dbo 应该是 db 里的纯净模式名, 不要使用 "", [], `` 等包裹
        /// </remarks>
        public string SchemaNamePure { get; set; }
        /// <summary>
        /// 示例:
        /// <list type="bullet">
        /// <item>sqlserver: [Table("t_test", Schema = "dbo")] => [dbo].[t_test]</item>
        /// <item>mysql: [Table("t_test")] => `t_test`</item>
        /// <item>mysql: 没有[Table]时 public class Person{...} => `Person`</item>
        /// </list>
        /// </summary>
        /// <remarks>
        /// 提示: 这个名字可以直接拼接到sql上, 如:
        /// <code>
        /// var sql = """select * from {EntityInfo.TableNameSeg}""";
        /// </code>
        /// </remarks>
        public string TableNameSeg { get; set; }
        #endregion

        /// <summary>
        /// 如: typeof(PersonEntity)
        /// </summary>
        public Type Type { get; set; }
        /// <summary>
        /// 通过 typeof(PersonEntity).GetClassFullName() 得到的名字
        /// </summary>
        public string TypeClassFullName { get; set; }
        /// <summary>
        /// 属性列集合
        /// </summary>
        public List<EntityPropertyInfo> EntityPropertyInfos { get; set; } = [];

        #region Clone
        public EntityInfo Clone()
        {
            var entityInfo = new EntityInfo()
            {
                TableNamePure = this.TableNamePure,
                SchemaNamePure = this.SchemaNamePure,
                TableNameSeg = this.TableNameSeg,
                Type = this.Type,
                TypeClassFullName = this.TypeClassFullName,
                EntityPropertyInfos = this.EntityPropertyInfos.Select(i => new EntityPropertyInfo()
                {
                    ColumnNameSeg = i.ColumnNameSeg,
                    ColumnNamePure = i.ColumnNamePure,
                    DefaultValue = i.DefaultValue,
                    IsColumn = i.IsColumn,
                    IsIgnoreInsert = i.IsIgnoreInsert,
                    IsIgnoreSelect = i.IsIgnoreSelect,
                    IsIgnoreUpdate = i.IsIgnoreUpdate,
                    IsPrimaryKey = i.IsPrimaryKey,
                    Order = i.Order,
                    PrimaryKeyStrategy = i.PrimaryKeyStrategy,
                    PropertyInfo = i.PropertyInfo,
                    Type = i.Type,
                    TypeClassFullName = i.TypeClassFullName,
                    IsNullAble = i.IsNullAble,
                    PropNamePure = i.PropNamePure,
                    PropNameSeg = i.PropNameSeg,
                    TypeName = i.TypeName,
                    IsDbString = i.IsDbString,
                    TypeCode = i.TypeCode,
                    IsEnum = i.IsEnum,
                    HasFlag = i.HasFlag,
                    IsColumnNameSegEqualPropNameSeg = i.IsColumnNameSegEqualPropNameSeg,

                    //JsonStore
                    JsonKey = i.JsonKey,
                    JsonBucket = i.JsonBucket,
                    JsonSelectKey = i.JsonSelectKey,
                    SelectFunc = i.SelectFunc,
                    SelectValueFunc = i.SelectValueFunc,
                    IsJsonArray = i.IsJsonArray,
                }).ToList()
            };
            return entityInfo;
        }
        #endregion
        /// <summary>
        /// 单个主键,当联合主键时,使用:
        /// <code>
        /// entityInfo.EntityPropertyInfos.Where(i => i.IsPrimaryKey).ToList()
        /// </code>
        /// </summary>
        public EntityPropertyInfo PrimaryKeyColumn => EntityPropertyInfos.FirstOrDefault(i => i.IsPrimaryKey);
    }

    public class EntityPropertyInfo
    {
        /// <summary>
        /// 通过 typeof(PersonEntity).GetProperties() 获取的
        /// </summary>
        public PropertyInfo PropertyInfo { get; set; }
        /// <summary>
        /// 通过 typeof(PersonEntity).GetProperty("Id").PropertyType 获取的
        /// </summary>
        public Type Type { get; set; }
        /// <summary>
        /// c#类型的默认值, 即: default(typeof(PersonEntity).GetProperty("Id").PropertyType)
        /// </summary>
        /// <remarks>
        /// 注意:
        /// <list type="bullet">
        /// <item><c>public int Id {get;set} = 5;</c> 这个的默认值是 0, 不是 5</item>
        /// <item><c>public int? Age {get;set;}</c> 这个的默认值是 null</item>
        /// </list>
        /// </remarks>
        public object DefaultValue { set; get; }
        /// <summary>
        /// 通过 typeof(PersonEntity).GetProperty("Id").PropertyType.GetClassFullName() 得到的名字
        /// </summary>
        public string TypeClassFullName { get; set; }
        /// <summary>
        /// 当前属性类型是否是可空的
        /// </summary>
        public bool IsNullAble { get; set; }
        /// <summary>
        /// 是否是枚举, 注意: 已经去除 nullable 的影响, 即: <c>public EnumSex? Sex {get;set} </c> 的 IsEnum 为 true
        /// </summary>
        public bool IsEnum { get; set; }
        /// <summary>
        /// 在 <seealso cref="IsEnum"/> 为 true 时, 进一步表示是否是位枚举
        /// </summary>
        public bool HasFlag { get; set; }
        /// <summary>
        /// 属性名称, 如: public string Name {get;set;} => Name
        /// </summary>
        /// <remarks>
        /// 注意: 因为属性名字可能是sql中的关键字, 所以这个 <c>PropNamePure</c> 不能用在sql拼接中. sql拼接时请使用 <seealso cref="PropNameSeg"/>
        /// </remarks>
        public string PropNamePure { get; set; }
        /// <summary>
        /// 带包裹的属性名称, 如: public string Name {get;set;} => `Name`
        /// </summary>
        /// <remarks>注意: 这个名字可以直接跟到select中做为别名, 如: select remark {PropNameSeg} from test</remarks>
        public string PropNameSeg { get; set; }
        /// <summary>
        /// 注意: 已经去除 nullable 的影响
        /// </summary>
        public TypeCode TypeCode { get; set; }
        /// <summary>
        /// 表示是否可以是db中的列.<br/>
        /// 当属性类型是简单类型(如: int?,string,enum 等)或有 [JsonStore] 标记的时候, 才可以认为这个属性能映射到db中的列.<br/>
        /// 如下面几种不能映射到db中的列(IsColumn=false):
        /// <list type="bullet">
        /// <item>/*元组不是简单类型*/  public (int id, string name) Info {get;set;}</item>
        /// <item>/*没有[JsonStore]*/  public PersonDetail Detail {get;set;}</item>
        /// </list>
        /// --------------------------疑问: 既然不认为是db中的列, 那为什么还保留这个属性的信息呢?--------------<br />
        /// 这是因为其他地方会使用到这些列, 如:
        /// <code>
        /// //生成嵌套类, 是先通过 db.GetEntityInfo&lt;PersonDto>() 获取到 EntityInfo, 然后再去分析装配的
        /// public class PersonDto
        /// {
        ///     public int Id { get;set;}
        ///     public int Age { get;set;}
        ///     public string Name { get;set;}
        ///     
        ///     public DetailDto Detail { get;set;}
        ///     public class DetailDto
        ///     {
        ///         public string Addr { get;set; }
        ///     }
        /// }
        /// db.SelectModel&lt;PersonDto>("select id `Id`,name `Name`,age `Age`, addr `Detail.Addr` from test");
        /// </code>
        /// --------------------------------------------------
        /// </summary>
        /// <remarks>
        /// 注意: 在拼接sql的时候需要将 IsColumn=false 的列过滤掉, 如:
        /// <code>
        /// entityInfo.EntityPropertyInfos
        ///           .Where(i=>!i.IsColumn &amp;&amp; !i.IsIgnoreSelect)
        ///           .Select(i => $"{i.SelectValueFunc("t")}).ToStringSeparated(",");
        /// //输出:
        /// //t.id,t.name,t.age
        /// </code>
        /// </remarks>
        public bool IsColumn { get; set; }
        /// <summary>
        /// 标记db里的列名称是否和属性名相等 (this.PropNameSeg == this.ColumnNameSeg), 如:
        /// <list type="bullet">
        /// <item>/*true*/ public int Id { get;set; }</item>
        /// <item>/*true*/ [Column("Id")]public int Id { get;set; }</item>
        /// <item>/*false*/ [Column("id")]public int Id { get;set; }</item>
        /// </list>
        /// </summary>
        /// <remarks>
        /// 注意: 这个可以用来决定生成 select 语句时,是否追加别名, 如: select `Id`, `name` `Name` from test
        /// </remarks>
        public bool IsColumnNameSegEqualPropNameSeg { get; set; }

        /// <summary>
        /// 可以直接参与sql拼接的列名 注意: 与[JsonStore]无关, 不会从这里面读, 如:
        /// <list type="bullet">
        /// <item>mysql [Column("id")] => `id`</item>
        /// <item>sqlserver [Column("id")] => [id]</item>
        /// <item>mysql 没有[Column]时 public int Id {get;set;} => `Id`</item>
        /// </list>
        /// </summary>
        /// <remarks>
        /// 注意: 这个名字可以直接拼到sql中, 如:
        /// <code>
        /// var sql = $"""select t.{ColumnName} from test t""";
        /// </code>
        /// </remarks>
        public string ColumnNameSeg { get; set; }
        /// <summary>
        /// 纯净列名, 可能是 [Column("colname")] 中配置的名字, 也可能是属性名称
        /// </summary>
        /// <remarks>
        /// 注意: 可以应用到元数据查询中, 如:
        /// <code>
        /// //考虑到 <c>ColumnNamePure</c> 可能含有特殊字符,所以需要使用 db.ProtectString()
        /// var sql = $"""select * from information_schema.columns t where t.column_name = {db.ProtectString(ColumnNamePure)}""";
        /// </code>
        /// </remarks>
        public string ColumnNamePure { get; set; }
        /// <summary>
        /// [Column(TypeName = "varchar(50)")] 中的配置, 没有配置则为null
        /// </summary>
        public string TypeName { get; set; }
        /// <summary>
        /// [Column(Order=xxxx)] 中的配置, 默认值:-1 (系统定义的默认值就是-1)
        /// </summary>
        public int Order { get; set; } = -1;
        /// <summary>
        /// 根据 [Column(TypeName = "")] 配置判断db中是否是存储为字符串
        /// </summary>
        public bool? IsDbString { get; set; }

        /// <summary>
        /// [JsonStore(Bucket=xxxx)]中的配置, 是db中列的名称, 如: <br/>
        /// <code>
        /// //mysql 建表语句: create table test(id int,ext json)
        /// //那么可以有如下 
        /// [JsonStore[Bucket="ext",Key="Addr"]]
        /// public string Addr {get;set;}
        /// </code>
        /// </summary>
        /// <remarks>
        /// 注意: 这里的 Bucket 配置和 [Column()] [Table()] 的配置一样, 使用的时候可以直接拼接, 所以如果有特殊字符请使用者自行处理, 如:
        /// <list type="bullet">
        /// <item>以数字开头或含有空格: [JsonStore(Bucket="\"123 ok\"")</item>
        /// <item>含有单引号,双引号,甚至换行符: [JsonStore(Bucket="\"p'\\"\\r\\np\"")</item>
        /// </list>
        /// </remarks>
        public string JsonBucket { get; set; }
        /// <summary>
        /// [JsonStore(Bucket="ext", Key=xxxx)]中 <c>Key</c> 的配置, 如:
        /// <code>
        /// //mysql 建表语句: create table test(id int,ext json)
        /// //那么可以有如下 
        /// [JsonStore[Bucket="ext",Key="Addr"]]
        /// public string Addr {get;set;}
        /// </code>
        /// 
        /// --------------------属性名中有特殊字符?-------------------------------<br/>
        /// 当有特殊字符时书写如下:
        /// <list type="bullet">
        /// <item>以数字开头或含有空格: [JsonStore(Bucket="...",Key="123 ok")</item>
        /// <item>含有单引号,双引号,甚至换行符: [JsonStore(Bucket="...",Key="p'\"\r\np")</item>
        /// </list>
        /// --------------------解释对特殊字符的防护处理-------------------------------<br/>
        /// 首先: 这里的 [JsonStore(Bucket="ext", Key=xxxx)] 中的Key 和 [Column(xxxx)] 中的写法不一致.
        /// <list type="number">
        /// <item>
        /// [Column("name")] 中的 name 是可以直接在sql中拼接(select t.name from test t), 转义的工作交给使用者可以让其有更多的控制权;
        /// </item>
        /// <item>
        /// 对于[JsonStore(Bucket="...",Key="name")] 中的 name, 有三个可能得应用场景(第二个场景要求必须配置的是没转义的,否则就必须反转义):
        /// <list type="number">
        /// <item> 用在函数参数时: db.Update&lt;Person>().SetExpr(i=>i.Detail.ModifyByDto(new{Addr="a-road"})) => json_object("xxxx","...")  </item>
        /// <item> 用于c#代码中合并同个bucket的多个key时: JsonObject.SetFluent("name","...") </item>
        /// <item> 用在 jsonpath 拼接时: '$."name"'</item>
        /// </list>
        /// </item>
        /// </list>
        /// </summary>
        public string JsonKey { get; set; }
        /// <summary>
        /// 当对json使用虚列时, 如: create table test(ext json, name varchar(50) generated always as (ext->>'$.DetailName')), 此时可以有配置:
        /// <code>
        /// public class Person
        /// {
        ///     [JsonStore(Bucket="ext", Key="DetailName", SelectKey="name")]
        ///     public string Name { get;set; }
        /// }
        /// </code>
        /// 这样生成的sql可以是:
        /// <code>select t.name from test t</code>
        /// 而不必是:
        /// <code>select json_value(t.ext,'$.DetailName' returning char) from test t</code>
        /// </summary>
        /// <remarks>
        /// 注意: 这个名字可以直接拼接到sql上, 如:
        /// <code>
        /// //不必担心特殊字符, 因为相信 [JsonStore(...SelectKey="xxxx")] 用户配置的时候已经对特殊字符进行了转义处理(这一点与 [Table] [Column] 以及 [JsonStore] 中的 Bucket 处理一直)
        /// var sql = """select t.xxxx from test""";
        /// </code>
        /// </remarks>
        public string JsonSelectKey { get; set; }
        /// <summary>
        /// 可能带列别名的select字句, 如:
        /// <list type="bullet">
        /// <item>t.name `Name`</item>
        /// <item>json_value(t.properties,'$.name') `Name`</item>
        /// </list>
        /// </summary>
        /// <remarks>
        /// 对比 <seealso cref="SelectValueFunc"/>
        /// </remarks>
        public Func<string, string> SelectFunc { get; set; }
        /// <summary>
        /// 不带别名的select字句, 如:
        /// <list type="bullet">
        /// <item>t.name</item>
        /// <item>json_value(t.properties,'$.name')</item>
        /// </list>
        /// </summary>
        /// <remarks>
        /// 对比 <seealso cref="SelectFunc"/>
        /// </remarks>
        public Func<string, string> SelectValueFunc { get; set; }

        /// <summary>
        /// 是否是JsonArray类型(当使用[JsonStiore]的时候)
        /// </summary>
        public bool IsJsonArray { get; set; }

        /*ignore notmapped*/
        public bool IsIgnoreUpdate { get; set; }
        public bool IsIgnoreSelect { get; set; }
        public bool IsIgnoreInsert { get; set; }

        /*主键*/
        public bool IsPrimaryKey { get; set; }
        public KeyStrategy? PrimaryKeyStrategy { get; set; }
    }
}
